package com.hotau.ui;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.hotau.R;

/**
 * @author Cruise
 * @date 2013-11-8
 */
public class Pull2RefreshView extends FrameLayout implements OnGestureListener {
	private ListView listView;

	protected Context context;

	private GestureDetector gestureDetector;

	private final static String TAG = "Pull2RefreshView";

	private MoveHandler moveHandler;

	private final static int STATE_IDLE = 0;
	private final static int STATE_PULL_DOWN = 1;
	private final static int STATE_PULL_OVER = 2;
	private final static int STATE_MOVING_UP = 4;
	private final static int STATE_REFRESHING = 8;
	private final static int STATE_REFRESH_CLOSING = 0x10;
	private final static int STATE_LOADING_MORE = 0x20;

	private boolean isAutoScroll;

	private int state = STATE_IDLE;

	private boolean isLoading;

	private int yOffset;

	SimpleDateFormat dateFormat;

	public Pull2RefreshView(Context context) {
		this(context, null);
	}

	public Pull2RefreshView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public Pull2RefreshView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.context = context;
		dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
		init();
	}

	private RotateAnimation mFlipAnimation;
	private RotateAnimation mReverseFlipAnimation;

	private ViewGroup headerView;
	private TextView mRefreshViewText;
	private ImageView mRefreshViewImage;
	private ProgressBar mRefreshViewProgress;
	private TextView mRefreshViewLastUpdated;

	private LayoutInflater mInflater;

	private View footerView;
	private View footerLoadingView;
	private View footerLoadMoreView;

	private void init() {
		initAnimation();

		mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		headerView = (ViewGroup) mInflater.inflate(R.layout.pull_to_refresh_header, this, false);
		mRefreshViewText = (TextView) headerView.findViewById(R.id.pull_to_refresh_text);
		mRefreshViewImage = (ImageView) headerView.findViewById(R.id.pull_to_refresh_image);
		mRefreshViewProgress = (ProgressBar) headerView.findViewById(R.id.pull_to_refresh_progress);
		mRefreshViewLastUpdated = (TextView) headerView.findViewById(R.id.pull_to_refresh_updated_at);

		mRefreshViewImage.setMinimumHeight(50);
		headerView.measure(0, 0);

		yOffset = -headerView.getMeasuredHeight();

		this.addView(headerView, new FrameLayout.LayoutParams(
				FrameLayout.LayoutParams.FILL_PARENT,
				FrameLayout.LayoutParams.WRAP_CONTENT));

		initMoreView();

		this.setListView(this.generateDefaultListView());
		this.hasMore(false);

		this.setClipChildren(false);
		this.setDrawingCacheEnabled(false);

		mRefreshViewImage.setDrawingCacheEnabled(false);
		headerView.setDrawingCacheEnabled(false);

		moveHandler = new MoveHandler();
		gestureDetector = new GestureDetector(this);
	}

	private boolean enableRefresh = true;

	public void enableRefresh(boolean enable) {
		enableRefresh = enable;
	}

	public void setListView(ListView lv) {
		if (this.listView != null && lv != listView) {
			listView.removeFooterView(footerView);
			this.removeView(this.listView);
		}

		listView = lv;
		listView.setCacheColorHint(Color.TRANSPARENT);
		this.addView(listView, new FrameLayout.LayoutParams(
				FrameLayout.LayoutParams.FILL_PARENT,
				FrameLayout.LayoutParams.FILL_PARENT));
	}

	private ListView generateDefaultListView() {
		return new ListView(context);
	}

	public ListView getListView() {
		return listView;
	}

	private void initMoreView() {
		footerView = mInflater
				.inflate(R.layout.pull_refresh_loading_more, null);
		footerView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				if (!isLoading && refreshListener != null) {
					setState(STATE_LOADING_MORE);
					updateLoadingView(true);
					refreshListener.onLoadingMore();
				}
			}
		});

		footerView.setLayoutParams(new AbsListView.LayoutParams(
				AbsListView.LayoutParams.FILL_PARENT,
				AbsListView.LayoutParams.WRAP_CONTENT));
		footerLoadingView = footerView
				.findViewById(R.id.pull_refresh_footer_loading_view);
		footerLoadMoreView = footerView
				.findViewById(R.id.pull_refresh_footer_load_more_view);
		this.updateLoadingView(false);
	}

	public void hasMore(boolean hasMore) {
		if (hasMore) {
			if (footerView == null) {
				this.initMoreView();
			}

			if (this.listView.getFooterViewsCount() == 0) {
				this.listView.addFooterView(footerView);
			}
		} else {
			if (footerView != null && listView.getFooterViewsCount() > 0) {
				this.listView.removeFooterView(footerView);
			}
		}
	}

	private void initAnimation() {
		mFlipAnimation = new RotateAnimation(0, -180,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		mFlipAnimation.setInterpolator(new LinearInterpolator());
		mFlipAnimation.setDuration(250);
		mFlipAnimation.setFillAfter(true);
		mReverseFlipAnimation = new RotateAnimation(-180, 0,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
		mReverseFlipAnimation.setDuration(250);
		mReverseFlipAnimation.setFillAfter(true);
	}

	public boolean dispatchTouchEvent(MotionEvent ev) {
		gestureDetector.onTouchEvent(ev);

		if (ev.getAction() == MotionEvent.ACTION_UP) {
			if (headerView.getTop() > 0) {
				setState(STATE_MOVING_UP);
				moveHandler.startScrollByDistance(headerView.getTop());
				Log.d(TAG, "moveUpAnimation -- top: " + headerView.getTop());
			} else if (headerView.getBottom() > 0) {
				isAutoScroll = true;
				setState(STATE_REFRESH_CLOSING, true);
			}
		} else if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			moveHandler.stop();
		}
		if (!interruptTouchEvent()) {
			super.dispatchTouchEvent(ev);
		}
		return true;
	}

	private boolean isHeaderShown() {
		Log.d(TAG, "isHeaderShown : " + headerView.getBottom());
		return headerView.getBottom() > 0;
	}

	@Override
	public boolean onDown(MotionEvent e) {
		return false;
	}

	@Override
	public void onShowPress(MotionEvent e) {

	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		return false;
	}

	private void updateStateByDirection(boolean isUp) {
		int absoluteOffset = Math.abs(yOffset);
		if (isUp) {
			if (absoluteOffset > headerView.getMeasuredHeight()) {
				setState(STATE_MOVING_UP);
			} else {
				setState(STATE_REFRESH_CLOSING);
			}
		} else {
			if (absoluteOffset > headerView.getMeasuredHeight()) {
				setState(STATE_PULL_OVER);
			} else {
				setState(STATE_PULL_DOWN);
			}
		}
	}

	private void updateLoadingView(final boolean isLoading) {
		if (this.footerView == null) {
			return;
		}

		this.post(new Runnable() {
			@Override
			public void run() {
				if (isLoading) {
					footerLoadingView.setVisibility(VISIBLE);
					footerLoadMoreView.setVisibility(GONE);
				} else {
					footerLoadingView.setVisibility(GONE);
					footerLoadMoreView.setVisibility(VISIBLE);
				}
			}
		});

		this.isLoading = isLoading;
	}

	private void setState(int state) {
		this.setState(state, false);
	}

	private boolean interruptTouchEvent() {
		return state == STATE_PULL_DOWN || state == STATE_PULL_OVER
				|| state == STATE_MOVING_UP || state == STATE_REFRESH_CLOSING;
	}

	private void setState(int state, boolean isForce) {
		if (this.state != state || isForce) {
			if (this.state != state && !isLoading
					&& (state & STATE_REFRESHING) != 0) {
				this.isLoading = true;
				if (this.refreshListener != null) {
					prepare2Refresh();
					refreshListener.onRefresh();
				}
				Log.d(TAG, "onRefresh");
			}

			if (state == STATE_IDLE) {
				moveHandler.startScrollByDistance(headerView.getBottom());
				this.updateLoadingView(false);
			}

			if (!isLoading) {
				if (state == STATE_PULL_DOWN) {
					this.pull2Refresh();
				} else if (state == STATE_PULL_OVER) {
					this.release2Refresh();
				}
			}

			this.state = state;

			if (state == STATE_REFRESH_CLOSING && isAutoScroll) {
				moveHandler.startScrollByDistance(headerView.getBottom());
			}

			Log.d(TAG, "current state: " + state);
		}
	}

	private void prepare2Refresh() {
		mRefreshViewImage.clearAnimation();
		mRefreshViewImage.setVisibility(View.GONE);
		mRefreshViewProgress.setVisibility(View.VISIBLE);
		// mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
		this.postInvalidate();
	}

	private void pull2Refresh() {
		Log.d(TAG, "pull2Refresh");
		mRefreshViewImage.setVisibility(View.VISIBLE);
		mRefreshViewProgress.setVisibility(View.GONE);
		mRefreshViewImage.clearAnimation();
		mRefreshViewImage.startAnimation(mReverseFlipAnimation);
		// mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);
		this.postInvalidate();
	}

	private void release2Refresh() {
		Log.d(TAG, "release2Refresh");
		mRefreshViewImage.setVisibility(View.VISIBLE);
		mRefreshViewProgress.setVisibility(View.GONE);
		headerView.postInvalidate();
		mRefreshViewImage.clearAnimation();
		mRefreshViewImage.startAnimation(mFlipAnimation);
//		 mRefreshViewText.setText(R.string.pull_to_refresh_release_label);
		this.postInvalidate();
	}

	private void resetOffset() {
		yOffset = -this.headerView.getMeasuredHeight();
	}

	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		flushLayout(changed, left, top, right, bottom);
	}

	private void flushLayout(boolean changed, int left, int top, int right, int bottom) {
		final int count = getChildCount();

		final int parentLeft = this.getPaddingLeft();
		final int parentRight = right - left - this.getPaddingRight();

		if (state == STATE_IDLE) {
			this.resetOffset();
		}

		final int parentTop = this.getPaddingTop() + yOffset;
		final int parentBottom = bottom - top - this.getPaddingBottom();

		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() != GONE) {
				final LayoutParams lp = (LayoutParams) child.getLayoutParams();

				final int width = child.getMeasuredWidth();
				final int height = child.getMeasuredHeight();

				int childLeft = parentLeft;
				int childTop = parentTop;

				if (i > 0) {
					childTop = parentTop + headerView.getMeasuredHeight();
				}

				final int gravity = lp.gravity;

				if (gravity != -1) {
					final int horizontalGravity = gravity
							& Gravity.HORIZONTAL_GRAVITY_MASK;
					final int verticalGravity = gravity
							& Gravity.VERTICAL_GRAVITY_MASK;

					switch (horizontalGravity) {
					case Gravity.LEFT:
						childLeft = parentLeft + lp.leftMargin;
						break;
					case Gravity.CENTER_HORIZONTAL:
						childLeft = parentLeft
								+ (parentRight - parentLeft - width) / 2
								+ lp.leftMargin - lp.rightMargin;
						break;
					case Gravity.RIGHT:
						childLeft = parentRight - width - lp.rightMargin;
						break;
					default:
						childLeft = parentLeft + lp.leftMargin;
					}

					switch (verticalGravity) {
					case Gravity.TOP:
						childTop = parentTop + lp.topMargin;
						break;
					case Gravity.CENTER_VERTICAL:
						childTop = parentTop
								+ (parentBottom - parentTop - height) / 2
								+ lp.topMargin - lp.bottomMargin;
						break;
					case Gravity.BOTTOM:
						childTop = parentBottom - height - lp.bottomMargin;
						break;
					default:
						childTop = parentTop + lp.topMargin;
					}
				}

				child.layout(childLeft, childTop, childLeft + width, childTop + height);
			}
		}
	}

	private int clipOffset(int distance) {
		int offset = 0;
		if (distance > 0) {
			offset = yOffset + distance + this.headerView.getMeasuredHeight() > this
					.getHeight() ? this.getHeight() - yOffset
					- this.headerView.getMeasuredHeight() : distance;
		} else {
			offset = (yOffset + distance) < -this.headerView
					.getMeasuredHeight() ? yOffset
					+ this.headerView.getMeasuredHeight() : distance;
		}

		return offset;
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		if (listView.getFirstVisiblePosition() == 0 && enableRefresh) {
			isAutoScroll = false;

			int headerVisibleHeight = (int) ((listView.getChildCount() > 0 ? listView
					.getChildAt(0).getTop() : 0) - distanceY);
			headerVisibleHeight = clipOffset(headerVisibleHeight);
			this.updateViewOffset(headerVisibleHeight, true);
			this.invalidate();
		}
		return false;
	}

	private void updateViewOffset(int offset, boolean needUpdateState) {
		Log.d(TAG, "updateViewOffset -- " + offset);
		offset = this.clipOffset(offset);

		if (offset == 0) {
			return;
		}

		this.yOffset += offset;

		if (needUpdateState) {
			this.updateStateByDirection(offset < 0);
		}
		this.flushLayout(false, 0, this.getTop(), this.getRight(),
				this.getBottom());
	}

	@Override
	public void onLongPress(MotionEvent e) {
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		return false;
	}

	class MoveHandler implements Runnable {
		private int distance;

		private long updateTime;

		private static final int step = 1;

		private int totalOffset = 0;

		public MoveHandler() {
		}

		public void stop() {
			isAutoScroll = false;
			Pull2RefreshView.this.removeCallbacks(this);
		}

		public void startScrollByDistance(int distance) {
			isAutoScroll = true;
			updateTime = System.currentTimeMillis();
			this.distance = distance;
			totalOffset = 0;
			Pull2RefreshView.this.post(this);
		}

		@Override
		public void run() {
			long currentTime = System.currentTimeMillis();
			long timeInterval = currentTime - updateTime;
			int offset = (int) (timeInterval * step);

			if (totalOffset + offset > distance) {
				offset = distance - totalOffset;
			}

			updateViewOffset(-offset, false);
			Pull2RefreshView.this.invalidate();
			if (totalOffset >= distance || offset == 0) {
				stop();

				if (state == STATE_MOVING_UP) {
					setState(STATE_REFRESHING);
				} else {
					setState(STATE_IDLE);
				}
			} else {
				totalOffset += offset;
				updateTime = currentTime;
				Pull2RefreshView.this.post(this);
			}
		}
	}

	public void setLastUpdate(Date date) {
		mRefreshViewLastUpdated.setVisibility(VISIBLE);
		this.mRefreshViewLastUpdated.setText(
		// this.getContext().getResources().getString(R.string.pull_last_update)+
				dateFormat.format(date));
	}

	public void onRefreshComplete() {
		isLoading = false;
		if (state == STATE_LOADING_MORE) {
			updateLoadingView(false);
		}

		this.setState(STATE_IDLE);
	}

	private OnRefreshListener refreshListener;

	public OnRefreshListener getRefreshListener() {
		return refreshListener;
	}

	public void setRefreshListener(OnRefreshListener onRefreshListener) {
		this.refreshListener = onRefreshListener;
	}

	public interface OnRefreshListener {
		public void onRefresh();

		public void onLoadingMore();
	}
}
